package com.example.parking.application;

import com.example.parking.domain.model.ParkingOrder;
import com.example.parking.domain.model.ParkingSpace;
import com.example.parking.domain.model.Vehicle;
import com.example.parking.domain.repository.ParkingLotRepository;
import com.example.parking.domain.repository.ParkingOrderRepository;
import com.example.parking.domain.repository.ParkingSpaceRepository;
import com.example.parking.domain.repository.VehicleRepository;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.Duration;
import java.time.OffsetDateTime;

import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import io.github.resilience4j.ratelimiter.annotation.RateLimiter;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Timer;

@Service
public class EntryExitService {
    private final ParkingLotRepository parkingLotRepository;
    private final ParkingSpaceRepository parkingSpaceRepository;
    private final VehicleRepository vehicleRepository;
    private final ParkingOrderRepository parkingOrderRepository;
    private final StringRedisTemplate stringRedisTemplate;

    private final MeterRegistry meterRegistry;

    public EntryExitService(ParkingLotRepository parkingLotRepository,
                            ParkingSpaceRepository parkingSpaceRepository,
                            VehicleRepository vehicleRepository,
                            ParkingOrderRepository parkingOrderRepository,
                            StringRedisTemplate stringRedisTemplate,
                            MeterRegistry meterRegistry) {
        this.parkingLotRepository = parkingLotRepository;
        this.parkingSpaceRepository = parkingSpaceRepository;
        this.vehicleRepository = vehicleRepository;
        this.parkingOrderRepository = parkingOrderRepository;
        this.stringRedisTemplate = stringRedisTemplate;
        this.meterRegistry = meterRegistry;
    }

    @Transactional
    @RateLimiter(name = "entryExit")
    @CircuitBreaker(name = "entryExit", fallbackMethod = "entryFallback")
    public Long vehicleEntry(String lotCode, String spaceCode, String plateNo) {
        Timer.Sample sample = Timer.start(meterRegistry);
        // 校验停车场存在
        parkingLotRepository.findByCode(lotCode).orElseThrow(() -> new IllegalArgumentException("lot not found"));

        String lockKey = "lock:space:" + lotCode + ":" + spaceCode;
        Boolean acquired = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "1", Duration.ofSeconds(10));
        if (Boolean.FALSE.equals(acquired)) {
            throw new IllegalStateException("space locking");
        }
        try {
        // 校验车位可用
        ParkingSpace space = parkingSpaceRepository
                .findByLotCodeAndSpaceCode(lotCode, spaceCode)
                .orElseThrow(() -> new IllegalArgumentException("space not found"));
        if (space.getStatus() != ParkingSpace.SpaceStatus.AVAILABLE) {
            throw new IllegalStateException("space not available");
        }

        // 若车辆不存在则创建
        Vehicle vehicle = vehicleRepository.findByPlateNo(plateNo)
                .orElseGet(() -> vehicleRepository.save(Vehicle.builder()
                        .plateNo(plateNo)
                        .type("CAR")
                        .createdAt(OffsetDateTime.now())
                        .build()));

        // 开单、占位、可用车位数-1
        ParkingOrder order = ParkingOrder.builder()
                .lotCode(lotCode)
                .spaceCode(spaceCode)
                .plateNo(vehicle.getPlateNo())
                .entryTime(OffsetDateTime.now())
                .status(ParkingOrder.OrderStatus.OPEN)
                .build();
        parkingOrderRepository.save(order);

        space.setStatus(ParkingSpace.SpaceStatus.OCCUPIED);
        parkingSpaceRepository.save(space);

        var lot = parkingLotRepository.findByCode(lotCode).orElseThrow();
        lot.setAvailableSpaces(Math.max(0, lot.getAvailableSpaces() - 1));
        lot.setUpdatedAt(OffsetDateTime.now());
        parkingLotRepository.save(lot);

        return order.getId();
        } finally {
            stringRedisTemplate.delete(lockKey);
            sample.stop(Timer.builder("parking.entry.duration").register(meterRegistry));
        }
    }

    @Transactional
    @RateLimiter(name = "entryExit")
    @CircuitBreaker(name = "entryExit", fallbackMethod = "exitFallback")
    public BigDecimal vehicleExit(String lotCode, String spaceCode, String plateNo) {
        Timer.Sample sample = Timer.start(meterRegistry);
        // 查找未结订单
        ParkingOrder order = parkingOrderRepository
                .findFirstByPlateNoAndStatusOrderByEntryTimeDesc(plateNo, ParkingOrder.OrderStatus.OPEN)
                .orElseThrow(() -> new IllegalArgumentException("open order not found"));

        // 简单校验一致性
        if (!order.getLotCode().equals(lotCode) || !order.getSpaceCode().equals(spaceCode)) {
            throw new IllegalStateException("lot/space not match order");
        }

        // 释放车位、可用车位数+1
        ParkingSpace space = parkingSpaceRepository
                .findByLotCodeAndSpaceCode(lotCode, spaceCode)
                .orElseThrow(() -> new IllegalArgumentException("space not found"));
        space.setStatus(ParkingSpace.SpaceStatus.AVAILABLE);
        parkingSpaceRepository.save(space);

        var lot = parkingLotRepository.findByCode(lotCode).orElseThrow();
        lot.setAvailableSpaces(lot.getAvailableSpaces() + 1);
        lot.setUpdatedAt(OffsetDateTime.now());
        parkingLotRepository.save(lot);

        // 计费（按小时，向上取整，每小时5元）
        OffsetDateTime exitTime = OffsetDateTime.now();
        long minutes = Duration.between(order.getEntryTime(), exitTime).toMinutes();
        long hours = Math.max(1, (long) Math.ceil(minutes / 60.0));
        BigDecimal fee = new BigDecimal(hours).multiply(new BigDecimal("5")).setScale(2, RoundingMode.HALF_UP);

        order.setExitTime(exitTime);
        order.setStatus(ParkingOrder.OrderStatus.CLOSED);
        order.setFee(fee);
        parkingOrderRepository.save(order);

        sample.stop(Timer.builder("parking.exit.duration").register(meterRegistry));
        return fee;
    }

    // fallback methods
    private Long entryFallback(String lotCode, String spaceCode, String plateNo, Throwable t) {
        throw new IllegalStateException("entry temporarily unavailable");
    }

    private BigDecimal exitFallback(String lotCode, String spaceCode, String plateNo, Throwable t) {
        throw new IllegalStateException("exit temporarily unavailable");
    }
}


